package comet

import (
	"fmt"
	"io"
	"runtime/debug"
	"sync"
	"time"

	"gddgame.cc/galaxy/satellite"

	"gddgame.cc/galaxy/utils/def"

	"github.com/tinylib/msgp/msgp"
)

var (
	socketPool = sync.Pool{}
)

func init() {
	socketPool.New = func() interface{} {
		s := &Socket{}
		s.init()
		return s
	}
}

type Middleware func(socket *Socket, next Next) error

type DisconnectSocketHandler func(reason string)

type RefreshAuthHandler func(authority interface{})

type DispatchHandler func(event DE, packet Packet)

type Socket struct {
	def.Logger
	def.Store

	no         DN
	i          Interface
	client     *clientInline
	ExitStatus ExitStatus
	fns        []Middleware
	components def.ComponentSlice
	connected  bool

	authHandle        RefreshAuthHandler
	dispatchHandler   DispatchHandler
	disconnectHandler DisconnectSocketHandler

	transportIndex int
	compress       bool
}

func newSocket(no DN, i Interface, client *clientInline) *Socket {
	s := socketPool.Get().(*Socket)
	s.no = no
	s.i = i
	s.client = client
	s.Logger = client.Logger
	s.Store = client.Store
	s.ExitStatus = NoneExit
	s.connected = true
	s.transportIndex = 0
	s.compress = false
	return s
}

func (n *Socket) init() {
	n.fns = []Middleware{}
	n.components = []def.Component{}
}

func (n *Socket) Release() {
	n.disconnectHandler = nil
	n.dispatchHandler = nil
	n.authHandle = nil
	n.client = nil
	n.i = nil
	n.init()
	socketPool.Put(n)
}
func (s *Socket) ID() string {
	return s.client.ID()
}

func (s *Socket) Close() {
	if !s.connected {
		return
	}
	s.ExitStatus = ServerExit
	s.client.Close()
}

func (s *Socket) Disconnect() {
	if !s.connected {
		return
	}
	s.ExitStatus = ServerExit
	s.destroy("server namespace disconnect")
}

func (s *Socket) Disconnected(handler DisconnectSocketHandler) {
	s.disconnectHandler = handler
}

func (s *Socket) UpdateAuth(handler RefreshAuthHandler) {
	s.authHandle = handler
}

func (s *Socket) Dispatch(handler DispatchHandler) {
	s.dispatchHandler = handler
}

func (s *Socket) SetCompress(compress bool) {
	s.compress = compress
}

func (s *Socket) SetTransport(transportType satellite.TransportType) {
	s.transportIndex = s.client.server.transportTypes.Search(transportType)
}

func (s *Socket) Send(event DE, data []byte) {
	s.client.SendByte(s.no, MT(event), s.transportIndex, s.compress, data)
}

func (s *Socket) SendCompress(event DE, data []byte) {
	s.client.SendByte(s.no, MT(event), s.transportIndex, true, data)
}

func (s *Socket) SendData(event DE, data satellite.DataPacket) {
	s.client.SendData(s.no, MT(event), s.transportIndex, s.compress, data)
}

func (s *Socket) SendDataCompress(event DE, data satellite.DataPacket) {
	s.client.SendData(s.no, MT(event), s.transportIndex, true, data)
}

func (s *Socket) SendObject(event DE, data msgp.Encodable, callback func()) {
	s.client.Send(s.no, MT(event), s.transportIndex, s.compress, func(writer io.Writer) error {
		err := msgp.Encode(writer, data)
		if callback != nil {
			callback()
		}
		return err
	})
}

func (s *Socket) SendObjectCompress(event DE, data msgp.Encodable, callback func()) {
	s.client.Send(s.no, MT(event), s.transportIndex, true, func(writer io.Writer) error {
		err := msgp.Encode(writer, data)
		if callback != nil {
			callback()
		}
		return err
	})
}

func (s *Socket) SendError(err error) {
	s.client.SendError(err)
}

func (s *Socket) App() *AppPacket {
	return s.client.App()
}

func (s *Socket) Add(component def.Component) error {
	index := s.components.Search(component)
	if index >= 0 {
		return fmt.Errorf("Component %#v is exits", component)
	}
	err := component.Join(s)
	if err != nil {
		return err
	}
	s.components = s.components.Append(component)
	return nil
}

func (s *Socket) Delete(component def.Component) error {
	id := component.ID()
	index := s.components.Search(component)
	if index == -1 {
		return fmt.Errorf("Component %s is not exits", id)
	}
	err := component.Remove(s)
	if err != nil {
		return err
	}
	s.components = s.components.Remove(index)
	return nil
}

func (s *Socket) Group(category string) []def.Component {
	c := make([]def.Component, 0)
	for _, component := range s.components {
		if component.Group(category) {
			c = append(c, component)
		}
	}
	return c
}

func (s *Socket) Use(middleware Middleware) *Socket {
	s.fns = append(s.fns, middleware)
	return s
}

func (s *Socket) AcceptObject(packet Packet, data msgp.Decodable) {
	err := msgp.Decode(packet.Reader(), data)
	if err != nil {
		debug.PrintStack()
	}
}

func (s *Socket) Delay() (*time.Duration, *time.Time) {
	return s.client.pp.Delay()
}

func (s *Socket) run(next Next) error {
	l := len(s.fns)
	if l == 0 {
		return next(nil)
	}
	return s.m(l, 0, next)
}

func (s *Socket) m(all int, index int, next Next) error {
	return s.fns[index](s, func(err error) error {
		if err != nil {
			return next(err)
		} else if index+1 >= all {
			return next(nil)
		} else {
			return s.m(all, index+1, next)
		}
	})
}

func (s *Socket) onDisconnect() {
	if !s.connected {
		return
	}
	s.ExitStatus = ClientExit
	s.destroy("client namespace disconnect")
}

func (s *Socket) onClose() {
	if !s.connected {
		return
	}
	s.ExitStatus = ClientExit
	s.destroy("client close")
}

func (s *Socket) destroy(reason string) {
	if !s.connected {
		return
	}
	s.connected = false
	if s.disconnectHandler != nil {
		s.disconnectHandler(reason)
	}
	if s.i != nil {
		_ = s.i.Disconnect(s, reason)
	}
	for _, component := range s.components {
		_ = component.Remove(s)
	}
	_ = s.client.Disconnect(s)
	s.Release()
}

func (s *Socket) onAccept(mt MT, message satellite.Message) error {
	event := DE(mt)
	return s.run(func(err error) error {
		if err != nil {
			return err
		}
		if s.dispatchHandler != nil {
			s.dispatchHandler(event, message)
		}
		return nil
	})
}

func (s *Socket) Connect() {
	_ = s.run(func(err error) error {
		if err != nil {
			return err
		}
		if s.i != nil {
			err = s.i.Connect(s)
		}

		if err != nil {
			s.client.SendError(err)
			s.Disconnect()
		} else {
			s.connected = true
		}
		return err
	})
}
